﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Framework.Domain
{
    public sealed class DocumentCode : IComparable<DocumentCode>
    {
        private readonly string _value;
        public static Regex FormatExpression = new Regex(@"^([BEFCIKLQRVPS]\d\d\d[cdebxu])((\.){1}([A-Za-z0-9]{1,6}))?$", RegexOptions.Compiled);
        public static Regex ShellExpression = new Regex(@"^([S]\d\d\d[x])((\.){1}([A-Za-z0-9]{1,6}))?$", RegexOptions.Compiled);
        public const int MaxLength = 12;
        public static readonly DocumentCode Empty = new DocumentCode(string.Empty);

        public DocumentCode()
        {
            _value = string.Empty;
        }

        private DocumentCode(string value)
        {
            _value = value;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException">Thrown when the value parameter is Null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value parameter is to long.</exception>
        /// <exception cref="FormatException">Thrown when the value parameter does not match the document code format.</exception>
        public static DocumentCode Parse(string value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            if (value.IndexOf(".") > 5)
            {
                throw new ArgumentException(string.Format("Passed in value '{0}' has too long a document code prefix. It must be no longer than 5 characters.", value), "value");
            }

            if (value.Length > 12)
            {
                throw new ArgumentException(string.Format("Passed in value '{0}' is too long. Must be no longer than 12 characters.", value), "value");
            }

            if (value != string.Empty && !FormatExpression.Match(value).Success)
            {
                throw new FormatException(string.Format("Passed in value '{0}' did not match expression.", value));
            }

            return new DocumentCode(value);
        }

        public static bool TryParse(string value, out DocumentCode documentCode)
        {
            documentCode = null;

            if (value == null || value.Length > 13) return false;

            if (FormatExpression.Match(value).Success)
            {
                documentCode = new DocumentCode(value);

                return true;
            }

            return false;
        }

        public static IEnumerable<DocumentCode> ParseStringOfCodes(string codes)
        {
            var choices = codes.Split(';');
            if (!choices.Any()) return new DocumentCode[0];

            var codeList = choices.Select(x =>
            {
                DocumentCode choiceCode;
                return DocumentCode.TryParse(x, out choiceCode) ? choiceCode : null;
            });
            return codeList.Where(x => x != null).ToArray();
        }

        public bool StartsWith(string partialDocumentCode)
        {
            return _value.StartsWith(partialDocumentCode, StringComparison.OrdinalIgnoreCase);
        }

        public string ProjectCode
        {
            get
            {
                var match = FormatExpression.Match(_value);
                return match.Groups[4].Value;
            }
        }

        public string VanillaCode
        {
            get
            {
                var match = FormatExpression.Match(_value);
                return match.Groups[1].Value;
            }
        }

        public bool IsShell
        {
            get
            {
                var match = ShellExpression.Match(_value);
                return match.Success;
            }
        }

        #region Equality overrides

        // This is the the one equality rule for this object
        // All the other equality checks derive from this and mostly cover 
        // null reference and type conversion behavior
        private static bool EqualsImpl(DocumentCode a, DocumentCode b)
        {
            // assumes that null checks have already been perfomed
            return a._value == b._value;
        }

        public static bool Equals(DocumentCode firstComparable, DocumentCode secondComparable)
        {
            // If both are null, or both are same instance, return true.
            if (ReferenceEquals(firstComparable, secondComparable)) return true;

            // If one is null, but not both, return false.
            if (((Object)firstComparable == null) || ((Object)secondComparable == null)) return false;

            return EqualsImpl(firstComparable, secondComparable);
        }

        public bool Equals(DocumentCode other)
        {
            return Equals(this, other);
        }

        public override bool Equals(Object obj)
        {
            // If parameter is null return false.
            if (obj == null) return false;

            // If parameter cannot be cast to DocumentCode return false.
            var other = obj as DocumentCode;
            return (Object)other != null && EqualsImpl(this, other);
        }

        public static bool operator ==(DocumentCode firstComparable, DocumentCode secondComparable)
        {
            return Equals(firstComparable, secondComparable);
        }

        public static bool operator !=(DocumentCode firstComparable, DocumentCode secondComparable)
        {
            // Derivative compliment of operator ==
            return !(firstComparable == secondComparable);
        }

        #endregion

        #region Other System.Object overrides

        public override int GetHashCode()
        {
            return _value.GetHashCode();
        }

        public int CompareTo(DocumentCode other)
        {
            return string.Compare(_value, other._value);
        }

        public override string ToString()
        {
            return _value;
        }

        #endregion

        public static implicit operator string(DocumentCode documentCode)
        {
            return documentCode.ToString();
        }

        public static implicit operator DocumentCode(string value)
        {
            return Parse(value);
        }
    }
}
